package pers.amos.eshop.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.amos.eshop.model.ProductInventory;
import pers.amos.eshop.request.support.ProductInventoryDBUpdateRequest;
import pers.amos.eshop.request.support.ProductInventoryRefreshCacheRequest;
import pers.amos.eshop.service.ProductInventoryService;
import pers.amos.eshop.service.RequestAsyncProcessService;
import pers.amos.eshop.vo.Response;

/**
 * @author amos wong
 * @create 2020-11-16 10:09 上午
 */
@RestController
public class ProductInventoryController {

    @Autowired
    private ProductInventoryService productInventoryService;

    @Autowired
    private RequestAsyncProcessService requestAsyncProcessService;

    /**
     * 更新库存的接口
     *
     * @param productInventory
     * @return
     */
    @RequestMapping("/updateProductInventory")
    public Response updateProductInventory(ProductInventory productInventory) {
        Response response = null;
        System.out.println("===========日志===========: 接收到更新商品库存的请求，商品id=" + productInventory.getProductId() + ", 商品库存数量=" + productInventory.getInventoryCnt());

        try {
            // 封装成更新库存的request
            ProductInventoryDBUpdateRequest request =
                    new ProductInventoryDBUpdateRequest(productInventory, productInventoryService);
            // 异步执行更新库存的操作
            requestAsyncProcessService.process(request);
            response = new Response(Response.SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            response = new Response(Response.FAILURE);
        }

        return response;
    }

    /**
     * 查询商品库存的接口
     * 1.先查询一次redis缓存，如果有则直接返回
     * 2.采用等待超时机制，最多等待200ms 如果还是查不到缓存则直接查询一次mysql数据库
     *
     * @param productId
     * @return
     */
    @RequestMapping("/getProductInventory")
    public ProductInventory getProductInventory(Long productId) {
        ProductInventory productInventory = null;
        try {
            // 封装成request
            ProductInventoryRefreshCacheRequest request =
                    new ProductInventoryRefreshCacheRequest(productId, productInventoryService, false);
            // 放入到内存队列中
            requestAsyncProcessService.process(request);

            // 将请求扔给service异步去处理以后，就需要while(true)一会儿，在这里hang住
            // 去尝试等待前面有商品库存更新的操作，同时缓存刷新的操作，将最新的数据刷新到缓存中
            long startTime = System.currentTimeMillis();
            long endTime = 0L;
            long waitTime = 0L;

            while (waitTime <= 200) {
                System.out.println("===========日志===========:waitTime=" + waitTime);

                // 尝试去redis中读取一次商品库存的缓存数据
                productInventory = productInventoryService.getProductInventoryCache(productId);

                // 如果读取到了结果，那么就返回
                if (productInventory != null) {
                    System.out.println("===========日志===========: 在200ms内读取到了redis中的库存缓存，商品id=" + productInventory.getProductId() + ", 商品库存数量=" + productInventory.getInventoryCnt());
                    return productInventory;
                } else { // 如果没有读取到结果，那么等待一段时间
                    Thread.sleep(20);
                    endTime = System.currentTimeMillis();
                    waitTime = endTime - startTime;
                }
            }

            // 超时了 直接从数据库中读取库存信息
            productInventory = productInventoryService.findProductInventoryByProductId(productId);
            if (productInventory != null) {
                // 读取库存信息后 将数据刷新到缓存中
                // 这个过程，实际上是一个读操作的过程，但是没有放在队列中串行去处理，还是有数据不一致的问题
                // 读数据库-刷新数据到缓存不是原子操作，所以需要放到队列中去串行处理
                // 开启强制刷新缓存，为了防止第一种情况的发生：redis LRU算法清理了缓存 同时读请求会被过滤
                request = new ProductInventoryRefreshCacheRequest(productId, productInventoryService, true);
                requestAsyncProcessService.process(request);

                // 代码会运行到这里，只有三种情况：
                // 1、就是说，上一次也是读请求，数据刷入了redis，但是redis LRU算法给清理掉了，标志位还是false
                // 所以此时下一个读请求是从缓存中拿不到数据的，再放一个读Request进队列，让数据去刷新一下
                // 2、可能在200ms内，就是读请求在队列中一直积压着，没有等待到它执行（在实际生产环境中，基本是比较坑了）
                // 所以就直接查一次库，然后给队列里塞进去一个刷新缓存的请求
                // 3、数据库里本身就没有，缓存穿透，穿透redis，请求到达mysql库
                return productInventory;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new ProductInventory(productId, -1L);
    }
}
